// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license


#include "stdafx.h"
#include "WorldBuilder.h"
#include "Resource.h"
#include "SceneTreeCtrl.h"
#include "Shader.h"
#include "ShaderProgram.h"
#include "MainView.h"


#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


CSceneTreeCtrl::CSceneTreeCtrl()
:   WorldFollower(WorldFollower::IsNodeListener 
              | WorldFollower::IsMaterialListener
              | WorldFollower::IsShaderListener
              | WorldFollower::IsTextureListener
              | WorldFollower::IsMeshListener),
    UiFollower(UiFollower::IsSelectionListener),
    m_bLDragging(false), m_pDragImage(NULL), 
    m_hitemForceExpand(NULL)
{
}

CSceneTreeCtrl::~CSceneTreeCtrl()
{
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

BEGIN_MESSAGE_MAP(CSceneTreeCtrl, CViewTree)
    ON_NOTIFY_REFLECT(TVN_BEGINDRAG, &CSceneTreeCtrl::OnTvnBegindrag)
    ON_WM_MOUSEMOVE()
    ON_WM_LBUTTONUP()
    ON_WM_CREATE()
    ON_WM_CONTEXTMENU()
    ON_NOTIFY_REFLECT(TVN_SELCHANGED, &CSceneTreeCtrl::OnTvnSelchanged)
    ON_NOTIFY_REFLECT(NM_RCLICK, OnRClick)
END_MESSAGE_MAP()

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 


void CSceneTreeCtrl::OnTvnBegindrag(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
    *pResult = 0;

    m_hitemDrag = pNMTreeView->itemNew.hItem;
    m_hitemDrop = NULL;

    m_pDragImage = CreateDragImage(m_hitemDrag);  // get the image list for dragging
    // CreateDragImage() returns NULL if no image list
    // associated with the tree view control
    if( !m_pDragImage )
        return;

    m_bLDragging = TRUE;
    m_pDragImage->BeginDrag(0, CPoint(-15,-15));
    POINT pt = pNMTreeView->ptDrag;
    ClientToScreen( &pt );
    m_pDragImage->DragEnter(NULL, pt);
    SetCapture();
    *pResult = 0;
}

void CSceneTreeCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
    HTREEITEM hitem;
    UINT flags;

    if (m_bLDragging)
    {
        POINT pt = point;
        ClientToScreen( &pt );
        CImageList::DragMove(pt); 
        hitem = HitTest(point, &flags); 
        if (hitem != m_hitemDrop) 
        { 
            CImageList::DragShowNolock(FALSE); 
            SelectDropTarget(hitem); 
            m_hitemDrop = hitem; 
            CImageList::DragShowNolock(TRUE); 
        } 
    }
    
    CTreeCtrl::OnMouseMove(nFlags, point);
}

void CSceneTreeCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
    CTreeCtrl::OnLButtonUp(nFlags, point);

    if (m_bLDragging)
    {
        m_bLDragging = FALSE;
        CImageList::DragLeave(this);
        CImageList::EndDrag();
        ReleaseCapture(); 

        if(m_pDragImage != NULL) 
        { 
            delete m_pDragImage; 
            m_pDragImage = NULL; 
        } 

        // Remove drop target highlighting
        SelectDropTarget(NULL);

        // m_hitemDrop is NULL when we drop on a different window
        if( m_hitemDrag == m_hitemDrop || m_hitemDrop == NULL)
        {
            return;
        }

        // If Drag item is an ancestor of Drop item then return
        HTREEITEM htiParent = m_hitemDrop;
        while( (htiParent = GetParentItem( htiParent )) != NULL )
        {
            if( htiParent == m_hitemDrag ) 
            {
                return;
            }
        }

        m_hitemForceExpand = m_hitemDrop;

        Selectable* draggedAsSelectable = (Selectable*)GetItemData(m_hitemDrag);
        Node * draggedNode = dynamic_cast<Node*>(draggedAsSelectable);

        Selectable* parentAsSelectable = (Selectable*)GetItemData(m_hitemDrop);
        Node * newParent = dynamic_cast<Node*>(parentAsSelectable);

        getWorld().getNodeManager().reparent(draggedNode, newParent);
    }
    CViewTree::OnLButtonUp(nFlags, point);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 


void CSceneTreeCtrl::onSelectionChanged(SelectionManager*)
{
    updateSelection();
}

void CSceneTreeCtrl::onNodeEvent(const NodeEvent& event_)
{
    switch(event_.type)
    {
    case NodeEvent::NodeRemoved:
        // Quick  and dirty optimization to account for  the fact  that
        // we get one "removed" notification per node when the scene is 
        // cleared. With this optimization we update the tree only upon 
        // the first notification, and in the N-1 remaining  ones we do
        // nothing
        if(getWorld().getNodeManager().getNodes().empty() 
            && m_selectionToItem.empty())
        {
            return;
        }
        // Fallthrough
    case NodeEvent::NodeAdded:
    case NodeEvent::NodeParentChanged:
        update();
        break;
    }
}

void CSceneTreeCtrl::onMaterialEvent(const MaterialEvent&)
{
    update();
}

void CSceneTreeCtrl::onShaderEvent(const ShaderEvent& event_)
{
    if(event_.type != ShaderEvent::UniformVariableChanged)
    {
        update();
    }
}

void CSceneTreeCtrl::onMeshEvent(const MeshEvent& /*event_*/) 
{
    update();
}

void CSceneTreeCtrl::onTextureAddedOrRemoved(TextureManager*, Texture*)
{
    update();
}

void CSceneTreeCtrl::onWorldChanged(World*) 
{
    update();
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void CSceneTreeCtrl::setItemSelectable(Selectable* selectable, 
                                       HTREEITEM item)
{
    SetItemData(item, (DWORD_PTR)selectable);
    m_selectionToItem[selectable] = item;
}

void CSceneTreeCtrl::update()
{
    if(!hasWorld())
    {
        return;
    }

    // Start from scratch
    DeleteAllItems();
    m_selectionToItem.clear();

    HTREEITEM hRoot = InsertItem(_T("World"), 0, 0);
    HTREEITEM hNodes = InsertItem(_T("Nodes"), 0, 0, hRoot);
    HTREEITEM hMats = InsertItem(_T("Materials"), 0, 0, hRoot);
    m_shadersTreeItem = InsertItem(_T("Shaders"), 0, 0, hRoot);
    m_texturesTreeItem = InsertItem(_T("Textures"), 0, 0, hRoot);
    m_meshesTreeItem = InsertItem(_T("Meshes"), 0, 0, hRoot);
    m_meshBuffersTreeItem = InsertItem(_T("Mesh buffers"), 0, 0, hRoot);

    // TODO use a different icon for the different kinds of items

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Add nodes keeping their hierarchical order. Top-level nodes are 
    // handled in a different manner

    NodeManager& nodeManager = getWorld().getNodeManager();
    NodeSiblingIterator topLevel = nodeManager.nodesBegin();
    while(topLevel != nodeManager.nodesEnd())
    {
        // Add topLevel item
        HTREEITEM child = addNode(**topLevel, hNodes);

        // And now subtree of topLevel item
        NodeSiblingIterator it = topLevel.begin();
        for(; it != topLevel.end(); ++it)
        {
            build(**it, child);
        }

        topLevel = nodeManager.nextSibling(topLevel);
    }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Add materials (not hierarchical)

    MaterialManager& mm = getWorld().getMaterialManager();

    typedef MaterialManager::MaterialIt MatIt;
    for(MatIt it = mm.beginMaterials(); it != mm.endMaterials(); ++it)
    {
        const ref<Material>& mat = *it;

        HTREEITEM childItem = InsertItem(mat->getName().c_str(), 
                                         0, 0, hMats);
        setItemSelectable(mat.get(), childItem);

        size_t nbLayers = mat->getTextureLayerCount();
        for(size_t layerIndex = 0; layerIndex < nbLayers; ++layerIndex)
        {
            TextureLayer* layer = mat->getTextureLayer(layerIndex);
            childItem = InsertItem(layer->getTexture()->getName().c_str(), 
                                   0, 0, childItem);
            setItemSelectable(layer, childItem);
        }
    }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Add shaders (not hierarchical)

    ShaderManager& sm = getWorld().getShaderManager();

    typedef ShaderManager::ShaderIt ShaderIt;
    for(ShaderIt it = sm.beginShaders(); it != sm.endShaders(); ++it)
    {
        const ref<ShaderProgram>& prog = *it;
        HTREEITEM shaderProgramItem = InsertItem(prog->getName().c_str(), 
                                                 0, 0, m_shadersTreeItem);
        setItemSelectable(prog.get(), shaderProgramItem);

        if(prog->hasVertexShader())
        {
            HTREEITEM shaderItem = InsertItem(L"Vertex shader", 0, 0, 
                                              shaderProgramItem);
            setItemSelectable(prog->getVertexShader(), shaderItem);
        }

        if(prog->hasFragmentShader())
        {
            HTREEITEM shaderItem = InsertItem(L"Fragment shader", 0, 0, 
                                              shaderProgramItem);
            setItemSelectable(prog->getFragmentShader(), shaderItem);
        }

        size_t numUniforms = prog->getNumUniformVars();
        if(numUniforms > 0)
        {
            // Add the "uniforms" child element
            HTREEITEM uniformsItem = InsertItem(L"Uniform vars.", 0, 0, 
                                                shaderProgramItem);
            for(size_t i = 0; i < numUniforms; ++i)
            {
                UniformVar* uni = prog->getUniformVar(i);
                HTREEITEM childItem = 
                    InsertItem(uni->getName().c_str(), 0, 0, uniformsItem);
                setItemSelectable(uni, childItem);
            }
        }
    }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Add textures (not hierarchical)

    TextureManager& tm = getWorld().getTextureManager();

    typedef TextureManager::TextureIt TextureIt;
    for(TextureIt it = tm.beginTextures(); it != tm.endTextures(); ++it)
    {
        const ref<Texture>& tex = *it;
        HTREEITEM childItem = InsertItem(tex->getName().c_str(), 
                                         0, 0, m_texturesTreeItem);
        setItemSelectable(tex.get(), childItem);

    }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Add mesh buffers (not hierarchical)

    MeshManager& meshMngr = getWorld().getMeshManager();

    typedef MeshManager::MeshBufferIt MeshBuffIt;
    for(MeshBuffIt it = meshMngr.beginMeshBuffers(); it != meshMngr.endMeshBuffers(); ++it)
    {
        const ref<MeshBuffer>& meshBuff = *it;
        HTREEITEM childItem = InsertItem(meshBuff->getName().c_str(), 
                                         0, 0, m_meshBuffersTreeItem);
        setItemSelectable(meshBuff.get(), childItem);

    }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    
    Expand(hRoot, TVE_EXPAND);
    Expand(hNodes, TVE_EXPAND);
    Expand(m_shadersTreeItem, TVE_EXPAND);
    if(m_hitemForceExpand != NULL)
    {
        Expand(m_hitemForceExpand, TVE_EXPAND);
        m_hitemForceExpand = NULL;
    }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

    updateSelection();
}

HTREEITEM CSceneTreeCtrl::addNode(Node& node, const HTREEITEM item)
{
    HTREEITEM childItem = InsertItem(node.getName().c_str(), 0, 0, item);

    // It's important to use the intermediate Selectable pointer, if
    // we convert directly from Node* to DWORD_PTR then the conversion
    // back from DWORD_PTR to Selectable* does not work. In other words
    // since we are going to retrieve this as Selectable*, we better
    // put it in that way as well. This seems like a true pitfall of
    // C++. Note that, below, the addresses cointained in "ptr" and 
    // "selectable" are different (they actually differ by only 4 bytes)
    
    Node* ptr = &node;
    Selectable* selectable = ptr;
    SetItemData(childItem, (DWORD_PTR)selectable);
    m_selectionToItem[selectable] = childItem;

    return childItem;
}

void CSceneTreeCtrl::build(Node& root, const HTREEITEM item)
{
    HTREEITEM childItem = addNode(root, item);

    // It's important to use the intermediate Selectable pointer, if
    // we convert directly from Node* to DWORD_PTR then the conversion
    // back from DWORD_PTR to Selectable* does not work. In other words
    // since we are going to retrieve this as Selectable*, we better
    // put it in that way as well. This seems like a true pitfall of
    // C++. Note that, below, the addresses cointained in "ptr" and 
    // "selectable" are different (they actually differ by only 4 bytes)
    
    Node* ptr = &root;
    Selectable* selectable = ptr;
    SetItemData(childItem, (DWORD_PTR)selectable);
    m_selectionToItem[selectable] = childItem;

    for(NodeSiblingIterator it = root.getChildrenBegin(); it != root.getChildrenEnd(); ++it)
    {
        build(**it, childItem);
    }
}


int CSceneTreeCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (__super::OnCreate(lpCreateStruct) == -1)
        return -1;

    return 0;
}

void CSceneTreeCtrl::updateSelection()
{
    if(!hasUi())
    {
        return;
    }

    Selectable* selection = getUi().getSelectionManager().getSelection();
    if(selection == NULL)
    {
        SelectItem(NULL);
    }
    else
    {
        HTREEITEM item = m_selectionToItem[selection];
        SelectItem(item);
    }
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void CSceneTreeCtrl::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
    
    HTREEITEM item = pNMTreeView->itemNew.hItem;
    if(item == NULL) // Nothing selected
    {
        return;
    }

    DWORD_PTR data = GetItemData(item);
    Selectable* selection = (Selectable*)data;
    if(selection != NULL)
    {
        getUi().getSelectionManager().select(selection);
    }

    *pResult = 0;
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

void CSceneTreeCtrl::OnRClick(NMHDR*, LRESULT* pResult) 
{
    // Send WM_CONTEXTMENU to self
    SendMessage(WM_CONTEXTMENU, (WPARAM) m_hWnd, GetMessagePos());
    
    // Mark message as handled and suppress default handling
    *pResult = 1;
}

void CSceneTreeCtrl::OnContextMenu(CWnd*, CPoint point)
{
    if (point == CPoint(-1, -1))
    {
        return;
    }

    int menuId = -1;
    int subMenuIndex = 0;

    // Select clicked item:
    CPoint ptTree = point;
    ScreenToClient(&ptTree);

    UINT flags = 0;
    HTREEITEM hTreeItem = HitTest(ptTree, &flags);
    if(hTreeItem == NULL)
    {
        return;
    }

    SelectItem(hTreeItem);
    
    if(hTreeItem == m_shadersTreeItem)
    {
        subMenuIndex = 0;
        menuId = IDR_MENU_SHADERS;
    }
    else if(hTreeItem == m_texturesTreeItem)
    {
        subMenuIndex = 0;
        menuId = IDR_MENU_TEXTURES;
    }
    else
    {
        SelectionManager& sm = getUi().getSelectionManager();
        Selectable* sel = sm.getSelection();
        if(ShaderProgram* shader = dynamic_cast<ShaderProgram*>(sel))
        {
            menuId = IDR_MENU_SHADERPROGRAM;
        }
        else if(TextureLayer* texLayer = dynamic_cast<TextureLayer*>(sel))
        {
            menuId = IDR_MENU_TEXTURELAYER;
        }
        else
        {
            menuId = IDR_MAINFRAME;
            subMenuIndex = 3;
        }
    }

    ASSERT(menuId != -1);

    // TODO avoid duplication, this code is already in WorldBuilderView
    CMenu mainMenu;
    VERIFY(mainMenu.LoadMenu(menuId));
    CMenu* menu = &mainMenu;
    if(subMenuIndex != -1)
    {
        menu = mainMenu.GetSubMenu(subMenuIndex);
    }
    ASSERT(menu != NULL);
    
    theApp.GetContextMenuManager()->ShowPopupMenu(
        menu->GetSafeHmenu(), point.x, point.y, 
        theApp.getMainView(), TRUE);
}
